package com.yvon.maple.service.auth.authentication.granter;


import cn.hutool.core.util.StrUtil;
import com.yvon.boot.jwt.model.AuthInfo;
import com.yvon.boot.jwt.properties.JwtProperties;
import com.yvon.boot.jwt.utils.TokenUtil;
import com.yvon.boot.redis.service.RedisService;
import com.yvon.maple.pojo.LoginUser;
import com.yvon.maple.service.auth.domain.SecurityUser;
import com.yvon.maple.service.auth.param.LoginParam;
import com.yvon.maple.service.auth.service.SecurityUserService;
import com.yvon.maple.service.auth.enums.CodeTypeEnum;
import com.yvon.maple.service.auth.valicode.validate.code.processor.CodeValidator;
import com.yvon.maple.service.auth.vo.CaptchaVo;
import com.yvon.maple.service.auth.vo.ValidateVo;
import com.yvon.maple.utils.DataConverter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.yvon.boot.jwt.context.JwtConstants.*;
import static com.yvon.maple.constants.RedisKey.Auth.USER_LOGIN_INFO;

/**
 * 验证码TokenGranter
 *
 * @author : Yvon
 * @since : 2021-08-25
 */
@Slf4j
@RequiredArgsConstructor
public abstract class AbstractTokenGranter implements TokenGranter {
    protected final TokenUtil tokenUtil;

    protected final AuthenticationManager authenticationManager;

    protected final SecurityUserService securityUserService;

    protected final CodeValidator codeValidator;


    protected final RedisService redisService;

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 处理登录逻辑
     *
     * @param loginParam 登录参数
     * @return 认证信息
     */
    protected AuthInfo login(LoginParam loginParam) {

        if (StrUtil.isEmpty(loginParam.getUsername()) || StrUtil.isEmpty(loginParam.getPassword())) {
            throw new UsernameNotFoundException("请输入用户名或密码");
        }

        // 3. 验证登录
        UsernamePasswordAuthenticationToken passwordToken = new UsernamePasswordAuthenticationToken(loginParam.getUsername(), loginParam.getPassword());
        Authentication authentication = authenticationManager.authenticate(passwordToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
        storageUserInfo(securityUser);

        return this.createToken(securityUser);
    }


    /**
     * 验证图形验证码
     *
     * @param loginParam 登录请求参数
     * @author : Yvon / 2021-08-25
     */
    protected void validateCode(LoginParam loginParam) {
        CaptchaVo captcha = loginParam.getCaptcha();
        if (Objects.nonNull(captcha)) {
            ValidateVo imageCode = ValidateVo.builder().code(captcha.getCode()).key(captcha.getKey()).codeType(CodeTypeEnum.IMAGE).build();
            Assert.isTrue(codeValidator.validate(imageCode), "图形验证码错误");
        }
    }


    protected void storageUserInfo(SecurityUser user) {
        LoginUser loginUser = DataConverter.copy(user, LoginUser.class);
        redisService.set(USER_LOGIN_INFO + user.getUsername(), loginUser, jwtProperties.getExpire());
    }

    /**
     * 创建用户TOKEN
     *
     * @param user 用户
     * @return token
     */
    protected AuthInfo createToken(SecurityUser user) {
        Map<String, Object> map = new HashMap<>();
        map.put(JWT_KEY_USERNAME, user.getUsername());
        map.put(JWT_KEY_USER_ID, user.getId());
        map.put(JWT_KEY_NAME, user.getNickname());
        map.put(JWT_KEY_IS_ADMIN, user.getIsAdmin());
        map.put(AUTHENTICATION, user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()));

        return tokenUtil.createAuthInfo(map, null);
    }


}
